#include "main.h"
#include "glulx.h"
#include "webglk/glk.h"
#include <stdlib.h>
#include <stdio.h>
#include <string.h>

static void operands(uint8_t,uint16_t);
static void store(uint8_t,uint32_t);
static void branch(uint8_t);
static void call(uint32_t,uint32_t *,uint8_t);
static void callframe(uint8_t);
static void ret(uint32_t v);

static void binarySearch();
static void printString(uint32_t);
static void stkroll(uint32_t,int32_t);

#define L 1
#define S 0
#define OP0() operands(0,0)
#define OP1(a) operands(1,a)
#define OP2(a,b) operands(2,a|(b<<1))
#define OP3(a,b,c) operands(3,a|(b<<1)|(c<<2))
#define OP4(a,b,c,d) operands(4,a|(b<<1)|(c<<2)|(d<<3))
#define OP5(a,b,c,d,e) operands(5,a|(b<<1)|(c<<2)|(d<<3)|(e<<4))
#define OP8(a,b,c,d,e,f,g,h) operands(8,a|(b<<1)|(c<<2)|(d<<3)|(e<<4)|(f<<5)|(g<<6)|(h<<7))

void g_init()
{
	g.version=read32(4);
	if (g.version<0x00020000 || g.version>0x000301ff) fatal("Invalid glulx version: %d.%d.%d",g.version>>16,(g.version>>8)&0xff,g.version&0xff);
	g.ram=read32(8);
	g.stackSize=read32(0x14);
	stack=malloc(g.stackSize);
	g.iosys=0;
	SP=FP=0;

	call(read32(0x18),NULL,0);
}
void g_select(event_t *event)
{
	write32(g.event,event->type);
	write32(g.event+4,event->win);
	write32(g.event+8,event->val1);
	write32(g.event+0xc,event->val2);
}
uint32_t args[8],argm[8];
void g_run()
{
	uint32_t op;
	uint32_t a,b,r,p,i,cargs[16];
	int32_t bit;
	do {
		op=rom[PC++];
		switch (op&0xc0)
		{
			case 0x80:
				op=((op&0x3f)<<8)|rom[PC++];
				break;
			case 0xc0:
				op=(op&0x0f)<<24;
				op|=rom[PC++]<<16;
				op|=rom[PC++]<<8;
				op|=rom[PC++];
				break;
		}
		switch(op)
		{
			case 0x00: break; //NOP
			case 0x10: OP3(L,L,S);
				store(2,args[0]+args[1]);
				break;
			case 0x11: OP3(L,L,S);
				store(2,args[0]-args[1]);
				break;
			case 0x12: OP3(L,L,S);
				store(2,args[0]*args[1]);
				break;
			case 0x13: OP3(L,L,S);
				store(2,(int32_t)args[0]/(int32_t)args[1]);
				break;
			case 0x14: OP3(L,L,S);
				store(2,(int32_t)args[0]%(int32_t)args[1]);
				break;
			case 0x15: OP2(L,S);
				store(1,-(int32_t)args[0]);
				break;
			case 0x18: OP3(L,L,S);
				store(2,args[0]&args[1]);
				break;
			case 0x19: OP3(L,L,S);
				store(2,args[0]|args[1]);
				break;
			case 0x1b: OP2(L,S);
				store(1,~args[0]);
				break;
			case 0x1e: OP3(L,L,S);
				store(2,args[0]>>args[1]);
				break;
			case 0x20: OP1(L);
				branch(0);
				break;
			case 0x22: OP2(L,L);
				if (!args[0]) branch(1);
				break;
			case 0x23: OP2(L,L);
				if (args[0]) branch(1);
				break;
			case 0x24: OP3(L,L,L);
				if (args[0]==args[1]) branch(2);
				break;
			case 0x25: OP3(L,L,L);
				if (args[0]!=args[1]) branch(2);
				break;
			case 0x26: OP3(L,L,L);
				if ((int32_t)args[0]<(int32_t)args[1])
					branch(2);
				break;
			case 0x27: OP3(L,L,L);
				if ((int32_t)args[0]>=(int32_t)args[1])
					branch(2);
				break;
			case 0x28: OP3(L,L,L);
				if ((int32_t)args[0]>(int32_t)args[1])
					branch(2);
				break;
			case 0x29: OP3(L,L,L);
				if ((int32_t)args[0]<=(int32_t)args[1])
					branch(2);
				break;
			case 0x2b: OP3(L,L,L);
				if (args[0]>=args[1]) branch(2);
				break;
			case 0x30: OP3(L,L,S);
				for (i=0;i<args[1];i++) cargs[i]=pop();
				callframe(2);
				call(args[0],cargs,args[1]);
				break;
			case 0x31: OP1(L);
				ret(args[0]);
				break;
			case 0x32: OP2(S,L);
				callframe(0);
				store(0,SP);
				branch(1);
				break;
			case 0x33: OP2(L,L);
				FP=args[1];
				ret(args[0]);
				break;
			case 0x34: OP2(L,L);
				for (i=0;i<args[1];i++) cargs[i]=pop();
				SP=FP;
				call(args[0],cargs,args[1]);
				break;
			case 0x40: OP2(L,S);
				store(1,args[0]);
				break;
			case 0x42: OP2(L,S);
				switch (argm[1])
				{
					case 8:
					case 9:
						store(1,args[0]&0xff);
						break;
					default: fatal("invalid copyb operand: %d",argm[1]);
				}
				break;
			case 0x48: OP3(L,L,S);
				store(2,read32(args[0]+(int32_t)args[1]*4));
				break;
			case 0x49: OP3(L,L,S);
				store(2,read16(args[0]+(int16_t)args[1]*2));
				break;
			case 0x4a: OP3(L,L,S);
				store(2,rom[args[0]+(int32_t)args[1]]);
				break;
			case 0x4b: OP3(L,L,S);
				bit=(int32_t)args[1];
				store(2,!!(rom[args[0]+(bit>>3)]&(1<<(bit&7))));
				break;
			case 0x4c: OP3(L,L,L);
				write32(args[0]+(int32_t)args[1]*4,args[2]);
				break;
			case 0x4d: OP3(L,L,L);
				write16(args[0]+(int32_t)args[1]*2,args[2]);
				break;
			case 0x4e: OP3(L,L,L);
				rom[args[0]+(int32_t)args[1]]=args[2];
				break;
			case 0x4f: OP3(L,L,L);
				bit=(int32_t)args[1];
				if (args[2]) rom[args[0]+(bit>>3)]|=1<<(bit&7);
				else rom[args[0]+(bit>>3)]&=~(1<<(bit&7));
				break;
			case 0x51: OP2(L,S);
				store(1,peek(SP-(1+args[0])*4));
				break;
			case 0x52: OP0();
				a=pop();
				b=pop();
				push(a);
				push(b);
				break;
			case 0x53: OP2(L,L);
				stkroll(args[0],args[1]);
				break;
			case 0x54: OP1(L);
				p=SP-args[0]*4;
				for (i=0;i<args[0];i++,p+=4)
					push(peek(p));
				break;
			case 0x70: OP1(L);
				if (g.iosys==2) glk_put_char(args[0]&0xff);
				else if (g.iosys!=0) fatal("invalid iosys for putchar %d",g.iosys);
				break;
			case 0x71: OP1(L);
				printNum(args[0]);
				break;
			case 0x72: OP1(L);
				printString(args[0]);
				break;
			case 0x100: OP3(L,L,S);
				r=0;
				switch (args[0])
				{
					case 0: r=0x00030100; break;
					case 1: r=0x00000001; break;
					case 4: if (args[1]<3) r=1; break;
					case 5: r=1; break;
				}
				store(2,r);
				break;
			case 0x102: OP1(S);
				store(0,read32(0x10));
				break;
			case 0x110: OP2(L,S);
				if (g.randi)
				{
					r=g.randc++;
					if (g.randc==g.randi) g.randc=0;
				}
				else
				{
					g.randa=0x015a4e35L*g.randa+1;
					r=g.randa;
				}
				if (args[0]==0) store(1,r);
				else if ((int32_t)args[0]<0) store(1,-(r%(-(int32_t)args[0])));
				else store(1,r%args[0]);
				break;
			case 0x120: OP0();
				done+=2;
				break;
			case 0x122: OP0();
				loadROM(0);
				g.iosys=0;
				SP=FP=0;
				call(read32(0x18),NULL,0);
				break;
			case 0x123: OP2(L,S);
				store(1,!save(read32(8),read32(0x10)-read32(8),argm[1],args[1]));
				break;
			case 0x124: OP2(L,S);
				store(1,load(read32(8),read32(0x10)-read32(8),&argm[1],&args[1])==0?1:-1);
				break;
			case 0x125: OP1(S);
				store(0,1);	// no saveundo
				break;
			case 0x130: OP3(L,L,S);
				for (i=0;i<args[1];i++) cargs[i]=pop();
				store(2,handleGlk(args[0],cargs,args[1]));
				break;
			case 0x149: OP2(L,L);
				g.iosys=args[0];
				break;
			case 0x151: OP8(L,L,L,L,L,L,L,S);
				binarySearch();
				break;
			case 0x160: OP2(L,S);
				callframe(1);
				call(args[0],NULL,0);
				break;
			case 0x161: OP3(L,L,S);
				cargs[0]=args[1];
				callframe(2);
				call(args[0],cargs,1);
				break;
			case 0x162: OP4(L,L,L,S);
				for (i=0;i<2;i++) cargs[i]=args[i+1];
				callframe(3);
				call(args[0],cargs,2);
				break;
			case 0x163: OP5(L,L,L,L,S);
				for (i=0;i<3;i++) cargs[i]=args[i+1];
				callframe(4);
				call(args[0],cargs,3);
				break;
			default: fatal("Unknown opcode %x",op);
		}

	} while (!done);
	done--;
}

static void operands(uint8_t n,uint16_t bits)
{
	uint32_t addr=PC+((n+1)>>1);
	uint32_t r,i;
	for (i=0;i<n;i++,bits>>=1)
	{
		if (i&1) argm[i]=rom[PC-1]>>4;
		else argm[i]=rom[PC++]&0xf;
		switch (argm[i])
		{
			case 0:
				args[i]=0;
				break;
			case 1:
				args[i]=(int32_t)((int8_t)rom[addr++]);
				break;
			case 2:
				args[i]=(int32_t)((int16_t)read16(addr));
				addr+=2;
				break;
			case 3:
				args[i]=(int32_t)read32(addr);
				addr+=4;
				break;
			case 7:
				r=read32(addr); addr+=4;
				if (bits&1) r=read32(r);
				args[i]=r;
				break;
			case 8:
				if (bits&1) args[i]=pop();
				break;
			case 9:
				r=rom[addr++];
				if (bits&1) r=peek(FP+LP+r);
				args[i]=r;
				break;
			case 13:
				r=rom[addr++];
				if (bits&1) r=read32(g.ram+r);
				args[i]=r;
				break;
			case 14:
				r=read16(addr);
				addr+=2;
				if (bits&1) r=read32(g.ram+r);
				args[i]=r;
				break;
			default: fatal("invalid operand type: %d",argm[i]);
		}
	}
	PC=addr;
}
static void store(uint8_t dest,uint32_t value)
{
	switch (argm[dest])
	{
		case 0: break;
		case 8:
			push(value);
			break;
		case 9:
			poke(FP+LP+args[dest],value);
			break;
		case 13:
		case 14:
			write32(g.ram+args[dest],value);
			break;
		default: fatal("invalid store type: %d",argm[dest]);
	}
}
static void branch(uint8_t b)
{
	if (args[b]==0 || args[b]==1) ret(args[b]);
	else PC+=(int32_t)args[b]-2;
}

static void call(uint32_t addr,uint32_t *args,uint8_t numArgs)
{
	uint32_t prev=0,*locals;
	uint16_t localinfo;
	uint8_t type,numLocals=0,prevsize=0;
	int i,j;

	locals=malloc(sizeof(uint32_t)*256);


	FP=SP;
	push(0);	//frame len
	push(0);	//locals pos
	type=rom[addr++];

	do {
		localinfo=read16(addr); addr+=2;
		locals[numLocals++]=localinfo;
		prev<<=16;
		prev|=localinfo;
		prevsize+=2;
		if (prevsize==4) { push(prev); prev=prevsize=0; }
	} while (localinfo);
	if (prevsize) push(prev<<16);

	LP=SP-FP;
	poke(FP+4,LP);
	prevsize=prev=0;
	for (i=0;i<numLocals;i++)
	{
		switch (locals[i]>>8)
		{
			case 0: break;
			case 4:
				if (prevsize) push(prev<<(8*(4-prevsize)));
				prevsize=prev=0;
				for (j=0;j<(locals[i]&0xff);j++)
				{
					if (type==0xc1 && j<numArgs)
						push(args[j]);
					else push(0); 
				}
				break;
			default: fatal("unknown local type: %d",locals[i]>>8);
		}
	}
	if (prevsize) push(prev<<(8*(4-prevsize)));
	poke(FP,SP-FP);
	if (type==0xc0)
	{
		uint32_t len=numArgs;
		while (len) push(args[--len]);
		push(numArgs);
	}
	PC=addr;
	free(locals);
}
static void callframe(uint8_t st)
{
	switch (argm[st])
	{
		case 0:
			push(0);
			push(0);
			break;
		case 8:
			push(3);
			push(0);
			break;
		case 9:
			push(2);
			push(args[st]);
			break;
		case 13:
		case 14:
			push(1);
			push(g.ram+args[st]);
			break;
		default: fatal("unknown callstore type: %d",argm[st]);
	}
	push(PC);
	push(FP);
}
static void ret(uint32_t v)
{
	uint32_t destaddr,desttype;

	SP=FP;
	if (FP==0)
	{
		done++;
		return;
	}
	FP=pop();
	LP=peek(FP+4);
	PC=pop();
	destaddr=pop();
	desttype=pop();
	switch (desttype)
	{
		case 0: break;
		case 1:
			write32(destaddr,v);
			break;
		case 2:
			poke(FP+LP+destaddr,v);
			break;
		case 3:
			push(v);
			break;
		default: fatal("unknown desttype: %d",desttype);
	}
}
static void binarySearch()
{
	uint32_t i,b=0,t=args[4],s,addr;
	uint8_t d,*key;

	key=malloc(args[1]);
	if (args[6]&1)
		for (i=0;i<args[1];i++)
			key[i]=rom[args[0]+i];
	else
	{
		i=0;
		if (args[1]>3) key[i++]=(args[0]>>24)&0xff;
		if (args[1]>2) key[i++]=(args[0]>>16)&0xff;
		if (args[1]>1) key[i++]=(args[0]>>8)&0xff;
		key[i++]=args[0]&0xff;
	}
	while (b<t)
	{
		s=(t+b)>>1;
		addr=args[2]+s*args[3];
		d=memcmp(rom+addr+args[5],key,args[1]);
		if (d==0)
		{
			if (args[6]&4) store(7,s);
			else store(7,addr);
			free(key);
			return;
		}
		if (d==1) t=s;
		else b=s+1;
	}
	if (args[6]&4) store(7,-1);
	else store(7,0);
	free(key);
}
static void printString(uint32_t addr)
{
	uint32_t node;
	uint8_t bit,ch,type=rom[addr++];
	switch (type)
	{
		case 0xe1: //compressed string
			bit=0;
			ch=rom[addr++];
			do {
				node=read32(read32(0x1c)+8);
				while (!rom[node])
				{
					node=read32(node+((ch&1)?5:1));
					ch>>=1;
					bit++;
					if (bit==8) {bit=0;ch=rom[addr++];}
				}
				switch (rom[node])
				{
					case 1: break;
					case 2: if (g.iosys==2) glk_put_char(rom[node+1]);
						else if (g.iosys!=0) fatal("Invalid string iosys: %d",g.iosys);
						break;
					default:fatal("invalid string node type: %x",rom[node]);
				}
			} while (rom[node]!=1);
			break;
		default: fatal("invalid string type %x",type);
	}
}
static void stkroll(uint32_t num,int32_t places)
{
	uint32_t start=SP-num*4;
	memcpy(stack+SP,stack+start,num*4);
	if (places>0)
	{
		places%=num;
		memcpy(stack+start,stack+SP+(num-places)*4,places*4);
		memcpy(stack+start+places*4,stack+SP,(num-places)*4);
	}
	else
	{
		places=(-places)%num;
		memcpy(stack+start,stack+SP+places*4,(num-places)*4);
		memcpy(stack+SP-places*4,stack+SP,places*4);
	}
}
